﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;

namespace ArgusPCLib
{
	public abstract class Flags<T> where T : struct
	{
		public T Value { get; set; }

		private Flags(T value)
		{
			this.Value = value;
		}

		public static Flags<T> FromEnum(T value)
		{
			TypeInfo ti = typeof(T).GetTypeInfo();
			if (ti.IsEnum == false)
				throw new GenericTypeParameterNotSupportetException(ti.Name + " is not an enum.");
			if (ti.GetCustomAttribute<FlagsAttribute>(false) == null)
				throw new GenericTypeParameterNotSupportetException(string.Format("{0} does not have the System.FlagsAttribute.", ti.Name));

			Type type = Enum.GetUnderlyingType(typeof(T));
			if (type == typeof(int))
				return new _Int32(value);
			if (type == typeof(uint))
				return new _UInt32(value);
			if (type == typeof(byte))
				return new _Byte(value);
			if (type == typeof(sbyte))
				return new _SByte(value);
			if (type == typeof(short))
				return new _Int16(value);
			if (type == typeof(ushort))
				return new _UInt16(value);
			if (type == typeof(long))
				return new _Int64(value);
			if (type == typeof(ulong))
				return new _UInt64(value);

			throw new GenericTypeParameterNotSupportetException();
		}

		public static Flags<T> FromEnum() { return Flags<T>.FromEnum(default(T)); }

		protected abstract long ToInt64(T value);
		protected abstract T ToEnum(long value);

		public long ToInt64() { return this.ToInt64(this.Value); }

		public bool HasFlag(T flag)
		{
			long v = this.ToInt64(this.Value);
			long f = this.ToInt64(flag);
			return (v & f) == f;
		}

		public T GetValue(T mask)
		{
			long v = this.ToInt64(this.Value);
			long m = this.ToInt64(mask);
			return this.ToEnum(v & m);
		}

		public T SetValue(T mask, T value)
		{
			long v = this.ToInt64(this.Value);
			long m = this.ToInt64(mask);
			long v2 = this.ToInt64(value);
			return this.ToEnum((v & ~m) | (v2 & m));
		}

		public T SetFlag(T flag, bool value)
		{
			long v = this.ToInt64(this.Value);
			long f = this.ToInt64(flag);
			if (value == true)
				v |= f;
			else
				v &= (~f);
			return this.ToEnum(v);
		}

		#region Subtypes
		#region Unsigned
		class _Byte : Flags<T>
		{
			public _Byte(T value)
				: base(value) { }

			protected override T ToEnum(long value)
			{
				return (T)Enum.ToObject(typeof(T), (byte)value);
			}

			protected override long ToInt64(T value)
			{
				return (long)((ulong)Convert.ToByte(value));
			}
		}

		class _UInt16 : Flags<T>
		{
			public _UInt16(T value)
				: base(value) { }

			protected override T ToEnum(long value)
			{
				return (T)Enum.ToObject(typeof(T), (UInt16)value);
			}

			protected override long ToInt64(T value)
			{
				return (long)((ulong)Convert.ToUInt16(value));
			}
		}

		class _UInt32 : Flags<T>
		{
			public _UInt32(T value)
				: base(value) { }

			protected override T ToEnum(long value)
			{
				return (T)Enum.ToObject(typeof(T), (UInt32)value);
			}

			protected override long ToInt64(T value)
			{
				return (long)((ulong)Convert.ToUInt32(value));
			}
		}

		class _UInt64 : Flags<T>
		{
			public _UInt64(T value)
				: base(value) { }

			protected override T ToEnum(long value)
			{
				return (T)Enum.ToObject(typeof(T), (UInt64)value);
			}

			protected override long ToInt64(T value)
			{
				return (long)((ulong)Convert.ToUInt64(value));
			}
		}
		#endregion

		#region Signed
		class _SByte : Flags<T>
		{
			public _SByte(T value)
				: base(value) { }

			protected override T ToEnum(long value)
			{
				return (T)Enum.ToObject(typeof(T), (SByte)value);
			}

			protected override long ToInt64(T value)
			{
				return (long)Convert.ToSByte(value);
			}
		}

		class _Int16 : Flags<T>
		{
			public _Int16(T value)
				: base(value) { }

			protected override T ToEnum(long value)
			{
				return (T)Enum.ToObject(typeof(T), (Int16)value);
			}

			protected override long ToInt64(T value)
			{
				return (long)Convert.ToInt16(value);
			}
		}

		class _Int32 : Flags<T>
		{
			public _Int32(T value)
				: base(value) { }

			protected override T ToEnum(long value)
			{
				return (T)Enum.ToObject(typeof(T), (Int32)value);
			}

			protected override long ToInt64(T value)
			{
				return (long)Convert.ToInt32(value);
			}
		}

		class _Int64 : Flags<T>
		{
			public _Int64(T value)
				: base(value) { }

			protected override T ToEnum(long value)
			{
				return (T)Enum.ToObject(typeof(T), (Int64)value);
			}

			protected override long ToInt64(T value)
			{
				return (long)Convert.ToInt64(value);
			}
		}
		#endregion
		#endregion
	}
}
